/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.auth.internal.kerberos.v5;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Date;

import javax.crypto.SecretKey;

import org.apache.harmony.security.asn1.ASN1Any;
import org.apache.harmony.security.asn1.ASN1BitString;
import org.apache.harmony.security.asn1.ASN1Constants;
import org.apache.harmony.security.asn1.ASN1Explicit;
import org.apache.harmony.security.asn1.ASN1Integer;
import org.apache.harmony.security.asn1.ASN1OctetString;
import org.apache.harmony.security.asn1.ASN1Sequence;
import org.apache.harmony.security.asn1.ASN1SequenceOf;
import org.apache.harmony.security.asn1.ASN1StringType;
import org.apache.harmony.security.asn1.ASN1Type;
import org.apache.harmony.security.asn1.BerInputStream;
import org.apache.harmony.security.asn1.BitString;
import org.apache.harmony.security.asn1.DerInputStream;

/**
 * @see http://www.ietf.org/rfc/rfc4120.txt
 */
public class KDCReply {

	/**
	 * Authentication Service request message type
	 */
	public static final int AS_REP = 11;

	/**
	 * Ticket-Granting Service request message type
	 */
	public static final int TGS_REP = 13;

	// type of a protocol message: AS_REP or TGS_REP
	private final int msgType;

	private final PrincipalName cname;

	private final String crealm;

	private final Ticket ticket;

	private final EncryptedData encPart;

	//
	//
	//
	private Date authtime;

	private Date starttime;

	private Date endtime;

	private Date renewtill;

	private String srealm;

	private PrincipalName sname;

	// session key
	private EncryptionKey key;

	private BitString flags;

	/**
	 * <pre>
	 *  KDC-REP         ::= SEQUENCE {
	 *       pvno            [0] INTEGER (5),
	 *       msg-type        [1] INTEGER (11 -- AS -- | 13 -- TGS --),
	 *       padata          [2] SEQUENCE OF PA-DATA OPTIONAL
	 *       -- NOTE: not empty --,
	 *       crealm          [3] Realm,
	 *       cname           [4] PrincipalName,
	 *       ticket          [5] Ticket,
	 *       enc-part        [6] EncryptedData
	 *       -- EncASRepPart or EncTGSRepPart,
	 *       -- as appropriate
	 *       }
	 * </pre>
	 */
	static final ASN1Sequence KDC_REP_ASN1 = new ASN1Sequence(new ASN1Type[] {
			new ASN1Explicit(0, ASN1Integer.getInstance()), // pvno
			new ASN1Explicit(1, ASN1Integer.getInstance()), // msg-type
			new ASN1Explicit(2, new ASN1SequenceOf(ASN1Any.getInstance())),
			// TODO should we define Realm type?
			new ASN1Explicit(3, ASN1StringType.GENERALSTRING), // crealm
			new ASN1Explicit(4, PrincipalName.ASN1), // cname
			new ASN1Explicit(5, Ticket.TICKET_ASN1), // ticket
			new ASN1Explicit(6, EncryptedData.ASN1), // enc-part
	}) {
		{
			setOptional(2); // padata
		}

		@Override
		protected Object getDecodedObject(BerInputStream in) throws IOException {

			final Object[] values = (Object[]) in.content;

			return new KDCReply(ASN1Integer.toIntValue(values[1]),
					(String) values[3], (PrincipalName) values[4],
					(Ticket) values[5], (EncryptedData) values[6]);
		}

		@Override
		protected void getValues(Object object, Object[] values) {
			throw new RuntimeException(); // FIXME message
		}
	};

	public static final ASN1Explicit AS_REP_ASN1 = new ASN1Explicit(
			ASN1Constants.CLASS_APPLICATION, AS_REP, KDC_REP_ASN1);

	private static final ASN1SequenceOf LAST_REQ = new ASN1SequenceOf(
			new ASN1Sequence(new ASN1Type[] {
					// TODO should we define Int32 type?
					new ASN1Explicit(0, ASN1Integer.getInstance()), // lr-type
					new ASN1Explicit(1, KerberosTime.getASN1()), // lr-value
			}));

	private static final ASN1Sequence HOST_ADDRESS = new ASN1Sequence(
			new ASN1Type[] {
					// TODO should we define Int32 type?
					new ASN1Explicit(0, ASN1Integer.getInstance()), // addr-type
					new ASN1Explicit(1, ASN1OctetString.getInstance()), // address
			});

	private static final ASN1Sequence ENC_KDC_REP_PART = new ASN1Sequence(
			new ASN1Type[] { new ASN1Explicit(0, EncryptionKey.ASN1), // key
					new ASN1Explicit(1, LAST_REQ), // last-req
					// TODO should we define UInt32 type?
					new ASN1Explicit(2, ASN1Integer.getInstance()), // nonce
					new ASN1Explicit(3, KerberosTime.getASN1()), // key-expiration
					// TODO TicketFlags type?
					new ASN1Explicit(4, ASN1BitString.getInstance()), // flags
					new ASN1Explicit(5, KerberosTime.getASN1()), // authtime
					new ASN1Explicit(6, KerberosTime.getASN1()), // starttime
					new ASN1Explicit(7, KerberosTime.getASN1()), // endtime
					new ASN1Explicit(8, KerberosTime.getASN1()), // renew-till
					// TODO should we define Realm type?
					new ASN1Explicit(9, ASN1StringType.GENERALSTRING), // srealm
					new ASN1Explicit(10, PrincipalName.ASN1), // sname
					new ASN1Explicit(11, HOST_ADDRESS), // caddr
			}) {
		{
			setOptional(3); // key-expiration
			setOptional(6); // starttime
			setOptional(8); // renew-till
			setOptional(11); // caddr
		}
	};

	// TODO: create const ENC_AS_REP_PART=25
	private static final ASN1Explicit ENC_AS_REP_PART = new ASN1Explicit(
			ASN1Constants.CLASS_APPLICATION, 25, ENC_KDC_REP_PART);

	private KDCReply(int msgType, String crealm, PrincipalName cname,
			Ticket ticket, EncryptedData encPart) {
		this.msgType = msgType;
		this.cname = cname;
		this.crealm = crealm;
		this.ticket = ticket;
		this.encPart = encPart;
	}

	//
	//
	//

	public void decrypt(SecretKey key) throws IOException {
		final DerInputStream in = new DerInputStream(new ByteArrayInputStream(
				encPart.decrypt(key)));

		final Object[] values = (Object[]) ENC_AS_REP_PART.decode(in);

		this.key = (EncryptionKey) values[0];
		flags = (BitString) values[4];
		authtime = (Date) values[5];
		starttime = (Date) values[6];
		endtime = (Date) values[7];
		renewtill = (Date) values[8];
		srealm = (String) values[9];
		sname = (PrincipalName) values[10];
	}

	public Date getAuthtime() {
		return authtime;
	}

	public PrincipalName getCname() {
		return cname;
	}

	public String getCrealm() {
		return crealm;
	}

	public EncryptedData getEncPart() {
		return encPart;
	}

	public Date getEndtime() {
		return endtime;
	}

	public BitString getFlags() {
		return flags;
	}

	public EncryptionKey getKey() {
		return key;
	}

	public int getMsgtype() {
		return msgType;
	}

	public Date getRenewtill() {
		return renewtill;
	}

	public PrincipalName getSname() {
		return sname;
	}

	public String getSrealm() {
		return srealm;
	}

	public Date getStarttime() {
		return starttime;
	}

	public Ticket getTicket() {
		return ticket;
	}

}
